package gtgold;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;
import java.io.*;

/**
 * Holds all game data that changes throughout the game such as party stats
 * 
 * @author Robert, jo
 */
public class GameData implements Serializable
{
	private static final long serialVersionUID = 1L;
	/*
	 * transient prevents manager from being saved into
	 * the object stream, since we don't need to save
	 * an instance of GameManager upon serialization
	 */
	transient public GameManager manager;
	public final Occupation job;
	public final List<Person> party;
	public final ItemVec inventory;
	public final DelayManager delays;

	public Location destination;
	public int distanceLeft;

	public Pace pace;
	public Ration ration;
	public boolean oxenTired;

	private Random gen = new Random();
	public int distanceTraveled = 0;
	public int daysElapsed;
	public String moveStatus = "Preparing to leave";
	
	/**
	 * Creates a new GameData object.
	 *
	 * @param manager the game manager
	 * @param job occupation of party leader
	 * @param names names of party members (up to 4)
	 */
	public GameData(GameManager manager, Occupation job, List<String> names)
	{
		this.manager = manager;
		this.job = job;
		this.pace = Pace.STEADY;
		this.ration = Ration.FILLING;
		this.delays = new DelayManager();
		this.oxenTired = false;

		List<Person> mutableParty = new ArrayList<Person>();
		for (String name : names)
		{
			mutableParty.add(new Person(name));
		}
		party = Collections.unmodifiableList(mutableParty);

		inventory = new ItemVec().set(Item.MONEY, job.startMoney);

		this.destination = TrailDataManager.startLocation;
		this.distanceLeft = 0;
	}

	/**
	 * Continue along the trail updating location information
	 */
	public void leaveLocation()
	{
		List<TrailSegment> exits = destination.exits;
		TrailSegment t = null;

		if (exits.size() > 1)
		{
			Map<TrailSegment, String> buttons = new HashMap<TrailSegment, String>();

			for (TrailSegment x : exits)
			{
				buttons.put(x, x.destination);
			}

			buttons = Collections.unmodifiableMap(buttons);
			t = manager
					.openOptionDialog(
							"There is a fork in the trail. Which way do you want to go?",
							buttons, true);
		} else if (exits.size() == 1)
		{
			t = exits.get(0);
		}

		if (t != null)
		{
			destination = TrailDataManager.locations.get(t.destination);
			distanceLeft = t.length;
		}
	}

	/**
	 * Attempt crossing the river, suffering penalties randomly
	 * 
	 * @param which river crossing option selected
	 */
	public void handleRiverCrossingResult(CrossingOption which)
	{
		if (which == null)
		{
			return;
		}

		String failmsg = null;

		if (which == CrossingOption.FORD)
		{
			if (gen.nextInt(4) == 0)
			{
				failmsg = "sank";
			}
		} else if (which == CrossingOption.CAULK)
		{
			if (gen.nextInt(5) == 0)
			{
				failmsg = "flipped over";
			}
		}

		if (failmsg == null)
		{
			manager.openInfoDialog("You crossed the river sucessfully.");
		} else
		{
			ItemVec losses = new ItemVec();

			for (Item i : Item.values())
			{
				if (i == Item.MONEY || inventory.get(i) < 5)
				{
					continue;
				}

				int loss = gen.nextInt((int) inventory.get(i));
				losses.set(i, -loss);

				if (gen.nextInt(loss) > 5)
				{
					break;
				}
			}

			inventory.addeq(losses);

			// Lost stuff
			String s = "Your wagon " + failmsg
					+ " while crossing the river. You lost a bunch of stuff.";
			manager.openInfoDialog(s);
			
			// Someone drowned
			if (numAlivePartyMembers() > 1) // leader can't drown (he's a fish!)
			{
				int i = gen.nextInt(party.size() - 1) + 1;
				if (party.get(i).kill())
					manager.openInfoDialog(party.get(i).name + " drowned");
			}
		}

		leaveLocation();
	}

	/**
	 * Present the player with river crossing options
	 * upon reaching a river location
	 */
	public void crossRiver()
	{

		// We already paid for the ferry and are finished waiting
		if (delays.ferryWait == 0)
		{
			delays.ferryWait = -1; // Negative values are normal, positive means we already paid and are waiting
			handleRiverCrossingResult(CrossingOption.FERRY);
		} else
		// Create menu so they can choose how to cross
		{
			// Use TreeMap so the buttons will appear in the order we want
			Map<CrossingOption, String> buttons = new TreeMap<CrossingOption, String>();
			String text = "<html>You have reached the " + destination.name;
			text += "<br/>It's " + destination.river.avgDepth
					+ " ft deep.";

			buttons.put(CrossingOption.FORD, "Ford the river");
			buttons.put(CrossingOption.CAULK,
					"Caulk the wagon and float across");
			buttons.put(CrossingOption.WAIT, "Wait for conditions to improve");

			if (destination.river.hasFerry)
			{
				text += "<br/>There's a ferry available which costs $5<br/>and will take 3 days to be ready.";
				if (inventory.get(Item.MONEY) >= 5)
				{
					buttons.put(CrossingOption.FERRY, "Take the ferry");
				}
			}

			if (destination.river.hasIndians)
			{
				text += "<br/>Local indians are willing to guide you<br/>across the river in exchange for 3 sets of clothing.";
				if (inventory.get(Item.CLOTHES) >= 3)
				{
					buttons.put(CrossingOption.INDIANS, "Hire indian guides");
				}
			}

			text += "</html>";
			
			buttons = Collections.unmodifiableMap(buttons);
			
			CrossingOption choice = manager.openOptionDialog(text
					+ " What do you want to do?", buttons, true);

			// Hiring the indians happens immediately, so don't put it as an elseif
			if (choice == CrossingOption.INDIANS)
			{
				inventory.set(Item.CLOTHES, inventory.get(Item.CLOTHES) - 3);
			}

			if (choice == CrossingOption.FERRY)
			{
				inventory.set(Item.MONEY, inventory.get(Item.MONEY) - 5);
				delays.ferryWait = 3;
			} else
			{
				handleRiverCrossingResult(choice);
			}
		}
	}

	/**
	 * Simulates a day in the life of the trail
	 * @return whether we moved or not
	 */
	public boolean update()
	{
		boolean moving = true;
		moveStatus = null; //So we can see if we forgot to set it later
		daysElapsed++;
		
		//check for starvation
		if (inventory.get(Item.FOOD) <= 0 && inventory.get(Item.OXEN) >= 1 
				&& party.get(0).health() < Person.OK)
		{
			String text = "You will surely starve without food. Would you like to slaughter an ox? You have ";
			text = text + (int)inventory.get(Item.OXEN) + " ox(en).";
			boolean oxkill = manager.openYesNoDialog(text);
			
			if (oxkill){
				int amount = 170 + gen.nextInt(64);
				inventory.add(Item.OXEN, -1);
				inventory.add(Item.FOOD, amount);
				manager.openInfoDialog("You slaughtered an ox and recovered " + amount + " pounds of food.");
			}
		}
		

		// Check whether we are resting or delayed
		if (delays.delayed())
		{
			moving = false;
			moveStatus = delays.reason();
		}

		// Check whether we can move
		if (moving && delays.brokenPiece != null){
			Item i = delays.brokenPiece;
			String singular = i.toString();
			singular = singular.substring(0, singular.length() - 1); //Chop off last letter, kind of a dirty hack

			if (inventory.get(i) >= 1){
				inventory.add(i, -1);
				manager.openInfoDialog("You replaced the broken " + singular + " with a spare.");
				delays.brokenPiece = null;
			} else {
				manager.openInfoDialog("Your wagon has a broken " + singular + " and you cannot continue until you have a spare to replace it.");
				moving = false;
				moveStatus = "Broken wagon";
			}
		}
		else if (moving && inventory.get(Item.OXEN) < 1)
		{
			manager.openInfoDialog("You cannot continue until you have oxen to pull the wagon.");
			moving = false;
			moveStatus = "No oxen";
		} else if (moving && inventory.weight() > 3500)
		{
			manager.openInfoDialog("You can only carry a maximum of 3500lbs in the wagon.");
			manager.openDropMenu();

			moving = (inventory.weight() <= 3500); // If they dropped enough stuff, we can continue
			if (!moving) {moveStatus = "Wagon overburdened";}
		}

		// Check if we are at a location
		if (moving && distanceLeft <= 0)
		{
			if (destination.isRiver())
			{
				crossRiver();
			} else
			{
				leaveLocation();
			}

			moving = distanceLeft > 0; // If they still didn't leave, stop moving
			if (!moving) {moveStatus = "Stopped";}
		}

		// Handle movement
		if (moving)
		{
			moveStatus = "Moving";
			
			double modifier = (oxenTired ? 0.75 : 1.0);
			modifier = modifier * 625 * (10 + inventory.get(Item.OXEN)) / (512 + inventory.weight());
			if (modifier > 5.0) {modifier = 4.0 + Math.sqrt(modifier - 4.0);}
			
			int dist = (int)(pace.index * modifier);
			
			
			if (dist > distanceLeft)
			{
				dist = distanceLeft;
			}

			distanceLeft -= dist;
			distanceTraveled += dist;

			if (distanceLeft <= 0 && destination == TrailDataManager.endLocation)
			{
				manager.openInfoDialog("Congratulations! You have survived the Oregon Trail!");
				OregonTrail.reset();
			}
		}

		// Decrement resting counts
		delays.update();

		// Handle eating food and health updates
		foodAndHealth(moving);
		return moving;
	}

	private void foodAndHealth(boolean moving) {
		double avgFoodLeft = inventory.get(Item.FOOD) / numAlivePartyMembers();
		Ration actualRation = Ration.foodAmountToRation(avgFoodLeft, ration);

		//If they are starving, they'll eat all remaining food
		double amountFoodEaten = (actualRation == Ration.NONE) ? 
									inventory.get(Item.FOOD) : actualRation.food * numAlivePartyMembers();
		inventory.add(Item.FOOD, -amountFoodEaten);
									
		for (Person p : party)
		{
			if (!p.alive()) {continue;}
			
			double healthMod = gen.nextDouble() * .3 - .25; // -.25 to +.05
			if (job == Occupation.DOCTOR) {healthMod += 0.09;}
			if (!moving) {healthMod += 0.1;} //Resting
			if (!moving && distanceLeft == 0) {healthMod += 0.05;} //Resting at a location is better

			
			String oldDiesease = p.disease;
			p.update(gen, actualRation, (moving ? pace : Pace.STOPPED), healthMod);
			String newDiesease = p.disease;

			if (!p.alive()) { manager.openInfoDialog(
					p.name + " has died" + (newDiesease != null ? (" of " + newDiesease) : "") + ".");
			}
			else if (oldDiesease == null && newDiesease != null) { manager.openInfoDialog(
					p.name + " has " + newDiesease + ".");
			}
			else if (oldDiesease != null && newDiesease == null) { manager.openInfoDialog(
					p.name + " has recovered.");
			}
		}
		
		if (numAlivePartyMembers() <= 0)
		{
			manager.openInfoDialog("All members of your party have died\nGAME OVER");
			OregonTrail.reset();
		}
	}

	/**
	 * Set rest duration
	 * 
	 * @param duration days to rest
	 */
	public void rest(int duration)
	{
		if (duration > delays.restLength)
		{
			delays.restLength = duration;
		}
	}

	/**
	 * Get current location
	 * @return NullObject if not at a location
	 */
	public Location Location() {return (distanceLeft > 0) ? TrailDataManager.nullLocation : destination;}
	
	public int numAlivePartyMembers()
	{
		int temp = 0;
		for (Person p : party)
		{
			if (p.alive())
			{
				++temp;
			}
		}
		return temp;
	}
}
